﻿import os
import requests
import re
import io
import base64
import webbrowser
import sys
from datetime import datetime
from urllib.parse import unquote, urlparse
from config_manager import get_config

# --- 配置 ---
# GITLAB_URL = os.getenv('GITLAB_URL', 'http://10.28.120.90')
# PRIVATE_TOKEN = os.getenv('GITLAB_PRIVATE_TOKEN')
# ASSIGNEE_USERNAME = 'meihaohui'
# 新的输出配置
OUTPUT_DIR = 'gitlab_report'
# HTML_FILE_NAME = f'report_{ASSIGNEE_USERNAME}.html' # 将在 main 函数中动态设置
# IMAGE_SUBDIR = 'images' # 将在 main 函数中动态设置
IMAGE_SUBDIR = 'images'


# headers = { 'PRIVATE-TOKEN': PRIVATE_TOKEN } # 将在 main 函数中动态设置

def fetch_all_issues(gitlab_url, headers, assignee_username):
    # 获取所有状态的 issue
    issues = []
    page = 1
    while True:
        params = {
            'assignee_username': assignee_username,
            'scope': 'all', # 获取所有状态
            'per_page': 100,
            'page': page
        }
        print(f"正在获取第 {page} 页的所有问题...")
        try:
            response = requests.get(f"{gitlab_url}/api/v4/issues", headers=headers, params=params, verify=False)
            response.raise_for_status()
            data = response.json()
            if not data: break
            issues.extend(data)
            page += 1
        except requests.exceptions.RequestException as e:
            print(f"请求API时发生错误: {e}")
            return None
    return issues

def download_image_locally(url, headers, save_path):
    """下载图片并保存到本地，返回相对路径"""
    try:
        response = requests.get(url, headers=headers, verify=False, timeout=15)
        response.raise_for_status()
        with open(save_path, 'wb') as f:
            f.write(response.content)
        return True
    except Exception as e:
        print(f"  -> [警告] 无法下载图片 {url}: {e}")
        return False

def main():
    try:
        config = get_config()
    except Exception as e:
        print(f"加载配置时出错: {e}")
        sys.exit(1)
        
    GITLAB_URL = config['url']
    PRIVATE_TOKEN = config['token']
    ASSIGNEE_USERNAME = config['username']
    
    headers = { 'PRIVATE-TOKEN': PRIVATE_TOKEN }
    HTML_FILE_NAME = f'report_{ASSIGNEE_USERNAME}.html'
    
    all_issues = fetch_all_issues(GITLAB_URL, headers, ASSIGNEE_USERNAME)
    if all_issues is None: return

    # 在内存中筛选 'opened' 状态
    issues = [issue for issue in all_issues if issue.get('state') == 'opened']
    print(f"\n已获取全部 {len(all_issues)} 条 issue，筛选后剩下 {len(issues)} 条 'opened' 状态的 issue。")

    if not issues:
        print("没有找到 'opened' 状态的问题。")
        return

    print("正在生成HTML报告并下载本地图片...")

    # --- 创建输出目录 ---
    output_path = os.path.join(OUTPUT_DIR, ASSIGNEE_USERNAME)
    image_dir_path = os.path.join(output_path, IMAGE_SUBDIR)
    os.makedirs(image_dir_path, exist_ok=True)
    
    html_file_path = os.path.join(output_path, HTML_FILE_NAME)

    # (HTML 模板和之前类似，但图片 src 会被修改)
    html_template = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GitLab 问题报告 - {assignee}</title>
    <style>
        * {{ box-sizing: border-box; }}
        body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; display: flex; height: 100vh; background-color: #f8f9fa; color: #212529; }}
        
        #sidebar {{
            flex: 0 0 30%; /* Initial width, allows shrinking but not growing */
            min-width: 250px; /* Minimum width */
            background-color: #fff;
            border-right: 1px solid #dee2e6;
            overflow-y: auto;
            resize: horizontal; /* Allows browser-native resizing as a fallback */
            padding-bottom: 20px;
        }}

        #resizer {{
            flex: 0 0 5px; /* Resizer width */
            background: #e9ecef;
            cursor: col-resize;
            border-left: 1px solid #dee2e6;
            border-right: 1px solid #dee2e6;
        }}
        #resizer:hover {{
            background: #ced4da;
        }}

        #main-content {{
            flex: 1 1 auto; /* Takes up remaining space */
            overflow-y: auto;
            padding: 25px;
            word-wrap: break-word; /* Prevents long words from overflowing */
        }}

        #sidebar h2 {{ padding: 20px; margin: 0; border-bottom: 1px solid #dee2e6; font-size: 1.2em; background-color: #f1f3f5; position: sticky; top: 0; z-index: 10; }}
        .issue-list {{ list-style-type: none; padding: 0; margin: 0; }}
        .issue-list-item a {{ display: block; padding: 15px 20px; text-decoration: none; color: #343a40; border-bottom: 1px solid #e9ecef; transition: background-color 0.2s; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }}
        .issue-list-item a:hover {{ background-color: #e9ecef; }}
        .issue-list-item a.active {{ background-color: #007bff; color: white; }}
        
        /* --- New Styles for Priority Groups --- */
        .priority-group summary {{
            padding: 15px 20px;
            font-weight: bold;
            cursor: pointer;
            background-color: #f8f9fa;
            border-bottom: 1px solid #dee2e6;
            border-top: 1px solid #dee2e6;
            margin-top: -1px;
            outline: none;
            transition: background-color 0.2s;
        }}
        .priority-group summary:hover {{
            background-color: #e9ecef;
        }}
        .priority-group[open] > summary {{
            background-color: #e2e6ea;
        }}
        .priority-group .issue-list a {{
            padding-left: 35px; /* Indent issue links */
        }}

        .issue-detail {{ display: none; }}
        .issue-detail.active {{ display: block; }}
        h1 {{ font-size: 1.8em; margin-top: 0; border-bottom: 2px solid #007bff; padding-bottom: 10px; }}
        .meta-info {{ background-color: #e9ecef; padding: 15px; border-radius: 8px; margin-bottom: 20px; font-size: 0.9em; display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px; }}
        .meta-item {{ display: flex; flex-direction: column; }}
        .meta-item strong {{ color: #495057; margin-bottom: 3px; }}
        .description-content {{ background: #fff; padding: 15px; border-radius: 5px; border: 1px solid #dee2e6; white-space: pre-wrap; line-height: 1.6; }}
        .description-content img {{ max-width: 100%; height: auto; border: 1px solid #dee2e6; border-radius: 5px; margin-top: 10px; vertical-align: middle; cursor: pointer; transition: opacity 0.2s; }}
        .description-content img:hover {{ opacity: 0.8; }}
        #welcome {{ text-align: center; padding-top: 100px; color: #6c757d; }}

        /* Lightbox Styles */
        #lightbox-overlay {{
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.8);
            display: none;
            justify-content: center;
            align-items: center;
            z-index: 1000;
        }}
        #lightbox-image {{
            max-width: 90%;
            max-height: 90%;
            object-fit: contain;
        }}
        #lightbox-close {{
            position: absolute;
            top: 20px;
            right: 35px;
            color: #fff;
            font-size: 40px;
            font-weight: bold;
            cursor: pointer;
            transition: color 0.2s;
        }}
        #lightbox-close:hover {{
            color: #bbb;
        }}
    </style>
</head>
<body>
    <div id="sidebar">
        <h2>问题列表 ({issue_count})</h2>
        {sidebar_links}
    </div>
    <div id="resizer"></div>
    <div id="main-content">
        <div id="welcome" class="issue-detail active">
            <h1>欢迎使用 GitLab 问题报告</h1>
            <p>请从左侧列表中选择一个问题以查看详情。</p>
        </div>
        {detail_views}
    </div>
    
    <!-- Lightbox container -->
    <div id="lightbox-overlay">
        <span id="lightbox-close">&times;</span>
        <img id="lightbox-image" src="">
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {{
            // Logic for issue selection
            const links = document.querySelectorAll('.issue-list-item a');
            const details = document.querySelectorAll('.issue-detail');
            links.forEach(link => {{
                link.addEventListener('click', function(event) {{
                    event.preventDefault();
                    const targetId = this.getAttribute('data-target');
                    details.forEach(detail => detail.classList.remove('active'));
                    document.getElementById(targetId).classList.add('active');
                    links.forEach(l => l.classList.remove('active'));
                    this.classList.add('active');
                }});
            }});

            // Logic for resizable sidebar
            const sidebar = document.getElementById('sidebar');
            const resizer = document.getElementById('resizer');
            
            let isResizing = false;

            resizer.addEventListener('mousedown', function(e) {{
                isResizing = true;
                document.addEventListener('mousemove', handleMouseMove);
                document.addEventListener('mouseup', stopResize);
            }});

            function handleMouseMove(e) {{
                if (!isResizing) return;
                // We calculate the new width based on the mouse position relative to the viewport
                const newWidth = e.clientX;
                // Constrain the width
                if (newWidth > 250 && newWidth < (window.innerWidth - 250)) {{
                    sidebar.style.flexBasis = newWidth + 'px';
                }}
            }}

            function stopResize() {{
                isResizing = false;
                document.removeEventListener('mousemove', handleMouseMove);
                document.removeEventListener('mouseup', stopResize);
            }}

            // --- New Lightbox Logic ---
            const lightboxOverlay = document.getElementById('lightbox-overlay');
            const lightboxImage = document.getElementById('lightbox-image');
            const lightboxClose = document.getElementById('lightbox-close');
            const mainContent = document.getElementById('main-content');

            // Use event delegation on the main content area
            mainContent.addEventListener('click', function(e) {{
                if (e.target.tagName === 'IMG') {{
                    lightboxImage.src = e.target.src;
                    lightboxOverlay.style.display = 'flex';
                }}
            }});

            function closeLightbox() {{
                lightboxOverlay.style.display = 'none';
                lightboxImage.src = ''; // Clear src
            }}

            lightboxClose.addEventListener('click', closeLightbox);
            lightboxOverlay.addEventListener('click', function(e) {{
                // Close only if the overlay itself (the background) is clicked
                if (e.target === lightboxOverlay) {{
                    closeLightbox();
                }}
            }});
            
            // Close with Escape key
            document.addEventListener('keydown', function(e) {{
                if (e.key === 'Escape' && lightboxOverlay.style.display === 'flex') {{
                    closeLightbox();
                }}
            }});
        }});
    </script>
</body>
</html>
    """
    
    sidebar_links_html = ""
    detail_views_html = ""

    # --- 新增：按优先级对问题进行分类 ---
    priorities = ['P0', 'P1', 'P2', 'P3']
    categorized_issues = {p: [] for p in priorities}
    categorized_issues['无优先级'] = []

    for issue in issues:
        issue_priority = '无优先级'
        labels = issue.get('labels', [])
        for p in priorities:
            if p in labels:
                issue_priority = p
                break # 找到最高优先级（P0, P1, ...）就停止
        categorized_issues[issue_priority].append(issue)
    # --- 分类结束 ---

    # --- 更新：按分类生成侧边栏 ---
    priority_order = priorities + ['无优先级']
    for priority in priority_order:
        issues_in_category = categorized_issues[priority]
        if not issues_in_category:
            continue

        sidebar_links_html += f"""
        <details class="priority-group" open>
            <summary>{priority} ({len(issues_in_category)} 个)</summary>
            <ul class="issue-list">
        """
        for issue in issues_in_category:
            issue_id = issue['id']
            issue_iid = issue['iid']
            title = issue.get('title', 'No Title')
            sidebar_links_html += f'<li class="issue-list-item"><a href="#" data-target="issue-{issue_id}">#{issue_iid} - {title}</a></li>'
        
        sidebar_links_html += """
            </ul>
        </details>
        """
    # --- 生成结束 ---


    for issue in issues:
        issue_id = issue['id']
        issue_iid = issue['iid']
        title = issue.get('title', 'No Title')
        
        # sidebar_links_html += f'<li class="issue-list-item"><a href="#" data-target="issue-{issue_id}">#{issue_iid} - {title}</a></li>'
        
        description_raw = issue.get('description') or ''
        web_url = issue.get('web_url', '')
        
        # --- 新的图片和描述处理逻辑 ---
        def replace_markdown_with_html_image(match):
            """re.sub 的替换函数，用于处理每个匹配到的图片"""
            relative_path = match.group(1)
            project_url = web_url.split('/-/issues/')[0] if '/-/issues/' in web_url else ''
            
            if not project_url:
                return "[图片路径解析失败]"

            full_url = f"{project_url}{relative_path}"
            
            try:
                original_filename = os.path.basename(unquote(urlparse(full_url).path))
                local_filename = f"{issue_id}_{issue_iid}_{original_filename}"
            except Exception:
                local_filename = f"{issue_id}_{issue_iid}_{base64.b64encode(relative_path.encode()).decode('utf-8')[:12]}.png"

            local_image_path = os.path.join(image_dir_path, local_filename)
            relative_image_path = os.path.join(IMAGE_SUBDIR, local_filename).replace('\\', '/')
            
            print(f"  -> 正在下载图片: {full_url[-50:]}")
            if download_image_locally(full_url, headers, local_image_path):
                # 返回替换后的 <img> 标签
                return f'<img src="{relative_image_path}" alt="Embedded Image">'
            else:
                return f'<br>[图片加载失败: {full_url}]<br>'

        # 使用 re.sub 和替换函数来原地替换所有图片
        final_description_html = re.sub(r"!\[.*?\]\((/uploads/[^)]+)\)", replace_markdown_with_html_image, description_raw)
        # --- 逻辑结束 ---

        created_at = datetime.fromisoformat(issue['created_at'].replace('Z', '+00:00')).strftime('%Y-%m-%d %H:%M:%S')
        updated_at = datetime.fromisoformat(issue['updated_at'].replace('Z', '+00:00')).strftime('%Y-%m-%d %H:%M:%S')
        
        detail_views_html += f"""
        <div id="issue-{issue_id}" class="issue-detail">
            <h1>#{issue_iid} - {title}</h1>
            <div class="meta-info">
                <div class="meta-item"><strong>负责人:</strong> {issue.get('assignee', {}).get('name', 'N/A')}</div>
                <div class="meta-item"><strong>报告人:</strong> {issue.get('author', {}).get('name', 'N/A')}</div>
                <div class="meta-item"><strong>状态:</strong> {issue.get('state', 'N/A')}</div>
                <div class="meta-item"><strong>标签:</strong> {', '.join(issue.get('labels', [])) or '无'}</div>
                <div class="meta-item"><strong>创建于:</strong> {created_at}</div>
                <div class="meta-item"><strong>更新于:</strong> {updated_at}</div>
                <div class="meta-item"><strong>链接:</strong> <a href="{web_url}" target="_blank">在 GitLab 中打开</a></div>
            </div>
            <div class="description">
                <h3>问题描述</h3>
                <div class="description-content">{final_description_html or "无描述。"}</div>
            </div>
        </div>
        """

    final_html = html_template.format(
        assignee=ASSIGNEE_USERNAME,
        issue_count=len(issues),
        sidebar_links=sidebar_links_html,
        detail_views=detail_views_html
    )

    with open(html_file_path, 'w', encoding='utf-8') as f:
        f.write(final_html)
    
    print(f"\n成功！HTML 报告已生成在目录: {os.path.abspath(output_path)}")
    
    try:
        webbrowser.open(f"file://{os.path.abspath(html_file_path)}")
    except Exception as e:
        print(f"无法自动打开浏览器，请手动打开文件。错误: {e}")

if __name__ == "__main__":
    import urllib3
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    main() 